package gotnames.web.st;

import gotnames.Utils;
import gotnames.dm.KTrans;
import gotnames.dm.ProfilePicture;
import gotnames.dm.ProfilePictureData;
import gotnames.dm.QueryBuilder;
import gotnames.dm.User;
import gotnames.dm.User.Gender;
import gotnames.email.InviteEmail;
import gotnames.web.GotNamesServlet.UserAuthenticator;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.mail.MessagingException;

import com.medallia.spider.DropdownElement;
import com.medallia.tiny.CollUtils;
import com.medallia.tiny.Empty;
import com.medallia.tiny.Funcs;
import com.medallia.tiny.Predicate;
import com.medallia.tiny.Predicate.Predicates;

/**
 * Task only available to users with {@link User#isAdmin()}. Allows adding new
 * users, editing all users in the group and also {@link DownloadArchiveTask}
 * and {@link UploadArchiveTask}.
 */
public class AdminTask extends GotNamesTask {
	
	@Input interface Params {
		String email();
		String firstName();
		String lastName();
		Gender gender();
		boolean admin();
		boolean disableEmail();
		byte[] profilePicture();
	}
	
	@Output interface Values {
		V<List<DropdownElement>> GENDER_OPTIONS = v();
		V<Collection<User>> USERS = v();
	}
	
	PostAction action(final User user, final Params p, DynamicInput di, PersistenceManager pm, UserAuthenticator userAuthenticator) {
		if (!user.isAdmin())
			return redirectToTask(GuessNamesTask.class);
		
		final ProfilePictureData profilePicture = ProfilePictureData.getProfilePicture(p.profilePicture());

		final String email = p.email();
		if (email != null) {
			// Check if the email is already in use
			User existingUser = User.getByEmail(pm, email);
			if (existingUser != null)
				return rawStringUtf8("Email address is already in use by user '" + existingUser.getFullName() + "'");
			
			User newUser = new KTrans<User>(pm) {
				@Override protected User call() {
					User u = User.newUser(user.getGroupKey());
					
					u.setEmail(email);
					u.setFirstName(Utils.notNull(p.firstName(), "First name is required"));
					u.setLastName(Utils.notNull(p.lastName(), "Last name is required"));
					u.setGender(Utils.notNull(p.gender(), "Gender is required"));
					u.setAdmin(p.admin());
					u.setDisableEmail(p.disableEmail());
					
					if (profilePicture != null)
						u.setProfilePictureInfo(profilePicture);
					
					pm.makePersistent(u);
					return u;
				}
			}.go();
			
			if (!newUser.isDisableEmail()) {
				try {
					InviteEmail.sendTo(newUser, true);
				} catch (IOException e) {
					return rawStringUtf8("Failed to send invite email to new user: " + e.getMessage());
				} catch (MessagingException e) {
					return rawStringUtf8("Failed to send invite email to new user: " + e.getMessage());
				}
			}
		}
		
		ProfilePicture.saveProfilePicture(user, pm, profilePicture);
		
		Query q = pm.newQuery(User.class);
		q.setFilter("groupKey == " + user.getGroupKey());
		q.setOrdering("firstName ASC, lastName ASC");
		
		Collection<User> users;
		try {
			users = updateUsers(user, Utils.<Collection<User>>cast(q.execute()), di, pm, userAuthenticator);
		} catch (IllegalArgumentException e) {
			return rawStringUtf8(e.getMessage());
		}
		
		attr(Values.GENDER_OPTIONS, DropdownElement.fromEnum(Gender.class, null));
		attr(Values.USERS, users);
		
		return null;
	}

	private Collection<User> updateUsers(final User loggedInUser, final Collection<User> allUsers, final DynamicInput di, PersistenceManager pm, final UserAuthenticator userAuthenticator) {
		final List<User> deletedUsers = CollUtils.filteredCopy(allUsers, new Predicate<User>() {
			@Override public boolean accept(User u) {
				return di.getInput(u.getKey() + "_delete", Boolean.TYPE);
			}
		});
		List<Long> deletedUserKeys = Funcs.map(deletedUsers, User.KEY_FUNC);
		
		final Collection<User> users = deletedUsers.isEmpty() ? allUsers : CollUtils.filteredCopy(allUsers, Predicates.not(inSet(deletedUsers)));
		
		for (final User u : deletedUsers) {
			new KTrans.Void(pm) {
				@Override protected void run() {
					pm.deletePersistent(u);
				}
			}.go();
		}

		for (final User u : users) {
			final Long key = u.getKey();

			final String email = di.getInput(key + "_email", String.class);
			if (email != null) {
				User newLoggedInUser = new KTrans<User>(pm) {
					@Override protected User call() {
						User.checkCanSetEmail(pm, u, email);
						
						u.setEmail(email);
						u.setFirstName(Utils.notNull(di.getInput(key + "_firstName", String.class), "First name is required"));
						u.setLastName(Utils.notNull(di.getInput(key + "_lastName", String.class), "Last name is required"));
						u.setGender(Utils.notNull(di.getInput(key + "_gender", Gender.class), "Gender is required"));
						u.setAdmin(di.getInput(key + "_admin", Boolean.TYPE));
						u.setDisableEmail(di.getInput(key + "_disableEmail", Boolean.TYPE));
						
						ProfilePictureData profilePicture = ProfilePictureData.getProfilePicture(di.getInput(key + "_profilePicture", byte[].class));
		
						if (profilePicture != null)
							u.setProfilePictureInfo(profilePicture);
		
						pm.makePersistent(u);
						
						if (loggedInUser.getKey().equals(u.getKey())) {
							// Prevent user from getting locked out
							if (!u.isAdmin())
								throw new IllegalArgumentException("Cannot disable admin for logged in user");
							
							return u;
						} else {
							return null;
						}
					}
				}.go();
				
				if (di.getInput(key + "_sendInvite", Boolean.TYPE)) {
					try {
						InviteEmail.sendTo(u, true);
					} catch (IOException e) {
						throw new IllegalStateException("Failed to send invite email to " + u.getFullName() + ": " + e.getMessage());
					} catch (MessagingException e) {
						throw new IllegalStateException("Failed to send invite email to " + u.getFullName() + ": " + e.getMessage());
					}
				}
				
				if (newLoggedInUser != null)
					userAuthenticator.userUpdated(newLoggedInUser);
			}
		}
		
		if (!deletedUsers.isEmpty()) {
			Collection<ProfilePicture> pictures = QueryBuilder.begin(pm, ProfilePicture.class).getAllByKey("userKey", deletedUserKeys);
			for (final ProfilePicture profilePicture : pictures) {
				new KTrans.Void(pm) {
					@Override protected void run() {
						pm.deletePersistent(profilePicture);
					}
				}.go();
			}
		}
		
		return users;
	}

	private static <X> Predicate<X> inSet(Collection<X> objects) {
		final Set<X> set = Empty.hashSet(objects);
		return new Predicate<X>() {
			@Override public boolean accept(X e) {
				return set.contains(e);
			}
		};
	}

	@Override public String getPageTitle() { return "Admin"; }

}
